﻿using System;
using System.Runtime.CompilerServices;
using Newtonsoft_X.Json;

namespace Net
{
    [Serializable]
    public struct Vector3 : IEquatable<Vector3>
    {
        public float x;
        public float y;
        public float z;

        public Vector3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Vector3(float x, float y)
        {
            this.x = x;
            this.y = y;
            z = 0f;
        }

        public Vector3(float value)
        {
            x = y = z = value;
        }

        public Vector3(Vector2 value, float z)
        {
            x = value.x;
            y = value.y;
            this.z = z;
        }

        public float this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0: return x;
                    case 1: return y;
                    case 2: return z;
                    default: throw new IndexOutOfRangeException("Invalid Vector3 index!");
                }
            }
            set
            {
                switch (index)
                {
                    case 0: x = value; break;
                    case 1: y = value; break;
                    case 2: z = value; break;
                    default: throw new IndexOutOfRangeException("Invalid Vector3 index!");
                }
            }
        }

        public void Set(float newX, float newY, float newZ)
        {
            x = newX;
            y = newY;
            z = newZ;
        }

        public static Vector3 Lerp(Vector3 a, Vector3 b, float t)
        {
            t = Mathf.Clamp01(t);
            return new Vector3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);
        }

        public static Vector3 LerpUnclamped(Vector3 a, Vector3 b, float t)
        {
            return new Vector3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);
        }

        public static Vector3 MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta)
        {
            Vector3 a = target - current;
            float magnitude = a.magnitude;
            if (magnitude <= maxDistanceDelta || magnitude < 1E-45f)
                return target;
            return current + a / magnitude * maxDistanceDelta;
        }

        public static bool MoveTowards(ref Vector3 current, Vector3 target, float maxDistanceDelta)
        {
            var a = target - current;
            var magnitude = a.magnitude;
            if (magnitude <= maxDistanceDelta || magnitude < 1E-45f)
            {
                current = target;
                return true;
            }
            current += a / magnitude * maxDistanceDelta;
            return false;
        }

        public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed)
        {
            return SmoothDamp(current, target, ref currentVelocity, smoothTime, maxSpeed, UnityEngine.Time.deltaTime);
        }

        public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime)
        {
            return SmoothDamp(current, target, ref currentVelocity, smoothTime, float.PositiveInfinity, UnityEngine.Time.deltaTime);
        }

        public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed, float deltaTime)
        {
            smoothTime = Mathf.Max(0.0001f, smoothTime);
            float num = 2f / smoothTime;
            float num2 = num * deltaTime;
            float d = 1f / (1f + num2 + 0.48f * num2 * num2 + 0.235f * num2 * num2 * num2);
            Vector3 vector = current - target;
            Vector3 vector2 = target;
            float maxLength = maxSpeed * smoothTime;
            vector = ClampMagnitude(vector, maxLength);
            target = current - vector;
            Vector3 vector3 = (currentVelocity + num * vector) * deltaTime;
            currentVelocity = (currentVelocity - num * vector3) * d;
            Vector3 vector4 = target + (vector + vector3) * d;
            if (Dot(vector2 - current, vector4 - vector2) > 0f)
            {
                vector4 = vector2;
                currentVelocity = (vector4 - vector2) / deltaTime;
            }
            return vector4;
        }

        public static Vector3 Scale(Vector3 a, Vector3 b)
        {
            return new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
        }

        public void Scale(Vector3 scale)
        {
            x *= scale.x;
            y *= scale.y;
            z *= scale.z;
        }

        public static Vector3 Cross(Vector3 lhs, Vector3 rhs)
        {
            return new Vector3(
                lhs.y * rhs.z - lhs.z * rhs.y,
                lhs.z * rhs.x - lhs.x * rhs.z,
                lhs.x * rhs.y - lhs.y * rhs.x);
        }

        public override int GetHashCode()
        {
            return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2;
        }

        public override bool Equals(object other)
        {
            return other is Vector3 other2 && Equals(other2);
        }

        public bool Equals(Vector3 other)
        {
            return this == other;
        }

        public static Vector3 Reflect(Vector3 inDirection, Vector3 inNormal)
        {
            return -2f * Dot(inNormal, inDirection) * inNormal + inDirection;
        }

        public static Vector3 Normalize(Vector3 value)
        {
            float num = Magnitude(value);
            return num > 1E-05f ? value / num : zero;
        }

        public void Normalize()
        {
            float num = Magnitude(this);
            if (num > 1E-05f)
                this /= num;
            else
                this = zero;
        }

        [JsonIgnore]
        public Vector3 normalized => Normalize(this);

        public static float Dot(Vector3 lhs, Vector3 rhs)
        {
            return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
        }

        public static Vector3 Project(Vector3 vector, Vector3 onNormal)
        {
            float num = Dot(onNormal, onNormal);
            return num < Mathf.Epsilon ? zero : onNormal * Dot(vector, onNormal) / num;
        }

        public static Vector3 ProjectOnPlane(Vector3 vector, Vector3 planeNormal)
        {
            return vector - Project(vector, planeNormal);
        }

        public static float Angle(Vector3 from, Vector3 to)
        {
            float num = Mathf.Sqrt(from.sqrMagnitude * to.sqrMagnitude);
            if (num < 1E-15f)
                return 0f;
            float f = Mathf.Clamp(Dot(from, to) / num, -1f, 1f);
            return Mathf.Acos(f) * 57.29578f;
        }

        public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis)
        {
            float num = Angle(from, to);
            float num2 = Mathf.Sign(Dot(axis, Cross(from, to)));
            return num * num2;
        }

        public static float Distance(Vector3 a, Vector3 b)
        {
            Vector3 vector = a - b;
            return Mathf.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
        }

        public static Vector3 ClampMagnitude(Vector3 vector, float maxLength)
        {
            return vector.sqrMagnitude > maxLength * maxLength ? vector.normalized * maxLength : vector;
        }

        public static float Magnitude(Vector3 vector)
        {
            return Mathf.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
        }

        public readonly float magnitude => Mathf.Sqrt(x * x + y * y + z * z);

        public static float SqrMagnitude(Vector3 vector)
        {
            return vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
        }

        public readonly float sqrMagnitude => x * x + y * y + z * z;

        public static Vector3 Min(Vector3 lhs, Vector3 rhs)
        {
            return new Vector3(Mathf.Min(lhs.x, rhs.x), Mathf.Min(lhs.y, rhs.y), Mathf.Min(lhs.z, rhs.z));
        }

        public static Vector3 Max(Vector3 lhs, Vector3 rhs)
        {
            return new Vector3(Mathf.Max(lhs.x, rhs.x), Mathf.Max(lhs.y, rhs.y), Mathf.Max(lhs.z, rhs.z));
        }

        public static Vector3 zero => zeroVector;
        public static Vector3 one => oneVector;
        public static Vector3 forward => forwardVector;
        public static Vector3 back => backVector;
        public static Vector3 up => upVector;
        public static Vector3 down => downVector;
        public static Vector3 left => leftVector;
        public static Vector3 right => rightVector;
        public static Vector3 positiveInfinity => positiveInfinityVector;
        public static Vector3 negativeInfinity => negativeInfinityVector;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator +(Vector3 a, Vector3 b) => new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator -(Vector3 a, Vector3 b) => new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator -(Vector3 a) => new Vector3(-a.x, -a.y, -a.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator *(Vector3 a, float d) => new Vector3(a.x * d, a.y * d, a.z * d);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator *(float d, Vector3 a) => new Vector3(a.x * d, a.y * d, a.z * d);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator /(Vector3 a, float d) => new Vector3(a.x / d, a.y / d, a.z / d);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator ==(Vector3 lhs, Vector3 rhs) => SqrMagnitude(lhs - rhs) < 9.9999994E-11f;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator !=(Vector3 lhs, Vector3 rhs) => !(lhs == rhs);

        public override string ToString()
        {
            return $"({x:F1}, {y:F1}, {z:F1})";
        }

        public string ToString(string format)
        {
            return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})";
        }

        public const float kEpsilon = 1E-05f;
        public const float kEpsilonNormalSqrt = 1E-15f;

        private static readonly Vector3 zeroVector = new Vector3(0f, 0f, 0f);
        private static readonly Vector3 oneVector = new Vector3(1f, 1f, 1f);
        private static readonly Vector3 upVector = new Vector3(0f, 1f, 0f);
        private static readonly Vector3 downVector = new Vector3(0f, -1f, 0f);
        private static readonly Vector3 leftVector = new Vector3(-1f, 0f, 0f);
        private static readonly Vector3 rightVector = new Vector3(1f, 0f, 0f);
        private static readonly Vector3 forwardVector = new Vector3(0f, 0f, 1f);
        private static readonly Vector3 backVector = new Vector3(0f, 0f, -1f);
        private static readonly Vector3 positiveInfinityVector = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
        private static readonly Vector3 negativeInfinityVector = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);

        public readonly float Length() => (float)Math.Sqrt(x * (double)x + y * (double)y + z * (double)z);
        public readonly float LengthSquared() => (float)(x * (double)x + y * (double)y + z * (double)z);
        public readonly float Sum => x + y + z;

        public static void Distance(ref Vector3 value1, ref Vector3 value2, out float result)
        {
            float num1 = value1.x - value2.x;
            float num2 = value1.y - value2.y;
            float num3 = value1.z - value2.z;
            result = (float)Math.Sqrt(num1 * num1 + num2 * num2 + num3 * num3);
        }

        public static float DistanceSquared(Vector3 value1, Vector3 value2)
        {
            float num1 = value1.x - value2.x;
            float num2 = value1.y - value2.y;
            float num3 = value1.z - value2.z;
            return num1 * num1 + num2 * num2 + num3 * num3;
        }

        public static void DistanceSquared(ref Vector3 value1, ref Vector3 value2, out float result)
        {
            float num1 = value1.x - value2.x;
            float num2 = value1.y - value2.y;
            float num3 = value1.z - value2.z;
            result = num1 * num1 + num2 * num2 + num3 * num3;
        }

        public static void Dot(ref Vector3 vector1, ref Vector3 vector2, out float result)
        {
            result = vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z;
        }

        public static void Normalize(ref Vector3 value, out Vector3 result)
        {
            float num1 = value.x * value.x + value.y * value.y + value.z * value.z;
            if (num1 < Mathf.Epsilon)
            {
                result = value;
            }
            else
            {
                float num2 = 1f / (float)Math.Sqrt(num1);
                result = new Vector3(value.x * num2, value.y * num2, value.z * num2);
            }
        }

        public static void Cross(ref Vector3 vector1, ref Vector3 vector2, out Vector3 result)
        {
            result = new Vector3(
                vector1.y * vector2.z - vector1.z * vector2.y,
                vector1.z * vector2.x - vector1.x * vector2.z,
                vector1.x * vector2.y - vector1.y * vector2.x);
        }

        public static void Reflect(ref Vector3 vector, ref Vector3 normal, out Vector3 result)
        {
            float num = vector.x * normal.x + vector.y * normal.y + vector.z * normal.z;
            result = new Vector3(
                vector.x - 2f * num * normal.x,
                vector.y - 2f * num * normal.y,
                vector.z - 2f * num * normal.z);
        }

        public static void Min(ref Vector3 value1, ref Vector3 value2, out Vector3 result)
        {
            result = new Vector3(
                Mathf.Min(value1.x, value2.x),
                Mathf.Min(value1.y, value2.y),
                Mathf.Min(value1.z, value2.z));
        }

        public static void Max(ref Vector3 value1, ref Vector3 value2, out Vector3 result)
        {
            result = new Vector3(
                Mathf.Max(value1.x, value2.x),
                Mathf.Max(value1.y, value2.y),
                Mathf.Max(value1.z, value2.z));
        }

        public static Vector3 Clamp(Vector3 value1, Vector3 min, Vector3 max)
        {
            return new Vector3(
                Mathf.Clamp(value1.x, min.x, max.x),
                Mathf.Clamp(value1.y, min.y, max.y),
                Mathf.Clamp(value1.z, min.z, max.z));
        }

        public static void Clamp(ref Vector3 value1, ref Vector3 min, ref Vector3 max, out Vector3 result)
        {
            result = new Vector3(
                Mathf.Clamp(value1.x, min.x, max.x),
                Mathf.Clamp(value1.y, min.y, max.y),
                Mathf.Clamp(value1.z, min.z, max.z));
        }

        public static void Lerp(ref Vector3 value1, ref Vector3 value2, float amount, out Vector3 result)
        {
            result = new Vector3(
                value1.x + (value2.x - value1.x) * amount,
                value1.y + (value2.y - value1.y) * amount,
                value1.z + (value2.z - value1.z) * amount);
        }

        public static Vector3 SmoothStep(Vector3 value1, Vector3 value2, float amount)
        {
            amount = Mathf.Clamp01(amount);
            amount = amount * amount * (3f - 2f * amount);
            return new Vector3(
                value1.x + (value2.x - value1.x) * amount,
                value1.y + (value2.y - value1.y) * amount,
                value1.z + (value2.z - value1.z) * amount);
        }

        public static void SmoothStep(ref Vector3 value1, ref Vector3 value2, float amount, out Vector3 result)
        {
            amount = Mathf.Clamp01(amount);
            amount = amount * amount * (3f - 2f * amount);
            result = new Vector3(
                value1.x + (value2.x - value1.x) * amount,
                value1.y + (value2.y - value1.y) * amount,
                value1.z + (value2.z - value1.z) * amount);
        }

        public static Vector3 Negate(Vector3 value) => new Vector3(-value.x, -value.y, -value.z);
        public static void Negate(ref Vector3 value, out Vector3 result) => result = new Vector3(-value.x, -value.y, -value.z);
        public static Vector3 Add(Vector3 value1, Vector3 value2) => new Vector3(value1.x + value2.x, value1.y + value2.y, value1.z + value2.z);
        public static void Add(ref Vector3 value1, ref Vector3 value2, out Vector3 result) => result = new Vector3(value1.x + value2.x, value1.y + value2.y, value1.z + value2.z);
        public static Vector3 Sub(Vector3 value1, Vector3 value2) => new Vector3(value1.x - value2.x, value1.y - value2.y, value1.z - value2.z);
        public static void Sub(ref Vector3 value1, ref Vector3 value2, out Vector3 result) => result = new Vector3(value1.x - value2.x, value1.y - value2.y, value1.z - value2.z);
        public static Vector3 Multiply(Vector3 value1, Vector3 value2) => new Vector3(value1.x * value2.x, value1.y * value2.y, value1.z * value2.z);
        public static void Multiply(ref Vector3 value1, ref Vector3 value2, out Vector3 result) => result = new Vector3(value1.x * value2.x, value1.y * value2.y, value1.z * value2.z);
        public static Vector3 Multiply(Vector3 value1, float scaleFactor) => new Vector3(value1.x * scaleFactor, value1.y * scaleFactor, value1.z * scaleFactor);
        public static void Multiply(ref Vector3 value1, float scaleFactor, out Vector3 result) => result = new Vector3(value1.x * scaleFactor, value1.y * scaleFactor, value1.z * scaleFactor);
        public static Vector3 Divide(Vector3 value1, Vector3 value2) => new Vector3(value1.x / value2.x, value1.y / value2.y, value1.z / value2.z);
        public static void Divide(ref Vector3 value1, ref Vector3 value2, out Vector3 result) => result = new Vector3(value1.x / value2.x, value1.y / value2.y, value1.z / value2.z);
        public static Vector3 Divide(Vector3 value1, float divider) => new Vector3(value1.x / divider, value1.y / divider, value1.z / divider);
        public static void Divide(ref Vector3 value1, float divider, out Vector3 result) => result = new Vector3(value1.x / divider, value1.y / divider, value1.z / divider);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator *(Vector3 value1, Vector3 value2) => new Vector3(value1.x * value2.x, value1.y * value2.y, value1.z * value2.z);
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator /(Vector3 value1, Vector3 value2) => new Vector3(value1.x / value2.x, value1.y / value2.y, value1.z / value2.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static UnityEngine.Vector3 operator +(UnityEngine.Vector3 a, Vector3 b) => new UnityEngine.Vector3(a.x + b.x, a.y + b.y, a.z + b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static UnityEngine.Vector3 operator -(UnityEngine.Vector3 a, Vector3 b) => new UnityEngine.Vector3(a.x - b.x, a.y - b.y, a.z - b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static UnityEngine.Vector3 operator *(UnityEngine.Vector3 a, Vector3 b) => new UnityEngine.Vector3(a.x * b.x, a.y * b.y, a.z * b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static UnityEngine.Vector3 operator /(UnityEngine.Vector3 a, Vector3 b) => new UnityEngine.Vector3(a.x / b.x, a.y / b.y, a.z / b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator +(Vector3 a, UnityEngine.Vector3 b) => new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator -(Vector3 a, UnityEngine.Vector3 b) => new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator *(Vector3 a, UnityEngine.Vector3 b) => new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector3 operator /(Vector3 a, UnityEngine.Vector3 b) => new Vector3(a.x / b.x, a.y / b.y, a.z / b.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator ==(UnityEngine.Vector3 lhs, Vector3 rhs) => (lhs - rhs).sqrMagnitude < 9.9999994E-11f;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator !=(UnityEngine.Vector3 lhs, Vector3 rhs) => !(lhs == rhs);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator UnityEngine.Vector3(Vector3 v) => new UnityEngine.Vector3(v.x, v.y, v.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Vector3(UnityEngine.Vector3 v) => new Vector3(v.x, v.y, v.z);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator UnityEngine.Vector4(Vector3 v) => new UnityEngine.Vector4(v.x, v.y, v.z);
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Vector3(UnityEngine.Vector4 v) => new Vector3(v.x, v.y, v.z);
    }
}